#!/usr/bin/perl

######################################################################
package Swignition::JsonParser;
######################################################################

use utf8;
use strict;

use Swignition::GenericParser;
use Swignition::RDFModel::Value;
use Data::Dumper;
use JSON;

use vars qw(@ISA);
@ISA = qw(Swignition::GenericParser);

our $JSOUT;
our $HttpCache;
our $BNodes;

BEGIN
{
	our $js_available = 1;
	eval 'use JavaScript::SpiderMonkey';
	$js_available = 0 if $@;
}

sub new
{
	my $class    = shift;
	my $source   = shift;
	my $opts     = shift;
	my $this     = Swignition::GenericParser::object_structure($source, $opts);
	
	bless $this, $class;	
	
	$this->find_base_uri;
	$this->find_namespaces;

	if ($this->{options}->{p_AUTO})
	{
		$this->{RDF}->{ $this->uri } = Swignition::RDFModel::Subject->new($this->uri);	

		$this->{ RDF }->{ $this->uri }->addVal('http://purl.org/dc/terms/description',
			Swignition::RDFModel::Value->new('Could not parse JSON without working jsonGRDDL.'))
			unless $this->parseJson( from_json($this->{Source}) );

		Swignition::GenericParser::HTTP::consume($this)
			if Swignition::GenericParser::HTTP::is_enabled($this);

		Swignition::GenericParser::Recursive::consume($this)
			if Swignition::GenericParser::Recursive::is_enabled($this);
		
		$this->smush;
	}
	
	return $this;
}

sub _bnode
{
	my $name = shift;
	
	unless ($Swignition::JsonParser::BNodes->{$name})
	{
		my $n = $name;
		$n =~ s/^_://;
		
		$Swignition::JsonParser::BNodes->{$name} = 
			Swignition::GenericParser::Utils::BNodeURI(undef, $n);
	}
	
	return $Swignition::JsonParser::BNodes->{$name};
}

sub _get
{
	my $this   = shift;
	my $uri    = shift;
	my $accept = shift || undef;
	
	my $URI = URI->new($uri);
	return if ($this->{NoFollow}->{ $URI->host });
	
	unless ($Swignition::JsonParser::HttpCache->{$uri})
	{
		my $req = HTTP::Request->new(GET => $uri);
		$req->header('Accept' => $accept)
			if (defined $accept);
		
		$Swignition::JsonParser::HttpCache->{$uri} =
			Swignition::Misc::get_ua->request($req);
	}
	
	return $Swignition::JsonParser::HttpCache->{$uri};
}

sub parseRdfObject
{
	my $this = shift;
	my $obj  = shift;
	my $i;
	
	$Swignition::JsonParser::BNodes = {};
	
	foreach my $subject (keys %$obj)
	{
		next if $subject =~ /^\$/;

		my $subjecturi = _bnode($subject);

		my $S = Swignition::RDFModel::Subject->new($subjecturi);
		
		foreach my $pred (keys %{ $obj->{$subject} })
		{
			foreach my $value (@{ $obj->{$subject}->{$pred} })
			{
				my $V;
				if ($value->{'type'} eq 'uri')
					{ $V = Swignition::RDFModel::Value->new($value->{'value'}, 1); }
				elsif ($value->{'type'} eq 'bnode')
					{ $V = Swignition::RDFModel::Value->new(_bnode($value->{'value'}), 1); }
				elsif ($value->{'type'} eq 'literal')
				{
					$V = Swignition::RDFModel::Value->new($value->{'value'});
					$V->setDatatype($value->{'datatype'})
						if ($value->{'datatype'});
					# Todo - lang
				}
				
				$S->addVal($pred, $V) if ($V);
			}
		}
		
		$this->mergeSubject($S);
		$i++;
	}
	
	return $i;
}

sub doGrddl
{
	my $this  = shift;
	my $obj   = shift;
	my $trans = shift;
	my $jsont = '_main';
	
	if ($trans =~ /^(.+)\#([A-Za-z0-9\._-]+)$/)
	{
		$trans = $1;
		$jsont = $2;
	}

	# Check that JS is available.
	return unless ($Swignition::JsonParser::js_available);

	# Open our library.
	my $jslib = __FILE__;
	$jslib =~ s|JsonParser.pm|ext/jsonobject.js|;
	my $jslib_data = '';
	open JSLIB, $jslib;
	while (<JSLIB>)
		{ $jslib_data .= $_; }
	close JSLIB;	
	
	# Load library into SpiderMonkey.
	my $JS = JavaScript::SpiderMonkey->new();
	$JS->init;
	$JS->function_set("return_to_perl", sub
		{
			my $v = shift;
			$Swignition::JsonParser::JSOUT = $v;
		});
	$JS->function_set("print_to_perl", sub
		{
			print @_;
		});
	my $rv = $JS->eval($jslib_data);
	
	# If library was successfully loaded...
	if ($Swignition::JsonParser::JSOUT)
	{
		$Swignition::JsonParser::JSOUT = undef;
	
		# Get our transformation
		my $r = _get($this, $trans,
			'application/ecmascript, application/x-ecmascript, text/ecmascript, application/javascript, text/javascript');
		
		# If we could fetch it OK...
		if (defined $r
		&&  $r->code == 200
		&&  $r->header('content-type') =~ m#^\s*((application|text)/(x-)?(java|ecma)script)\b#)
		{
			my $rv = $JS->eval($r->content);

			if ($rv)
			{
				my $src    = to_json($obj);
				my $killer = "return_to_perl(JSON.transform($src, $jsont));";
				$JS->eval($killer);
				
#				print $killer . "\n\n";
#				print $Swignition::JsonParser::JSOUT . "\n--------\n\n";
				
				return $this->parseRdfObject(from_json($Swignition::JsonParser::JSOUT))
					if ($Swignition::JsonParser::JSOUT);
			}
		}
	}
}

sub parseJson
{
	my $this = shift;
	my $obj  = shift;
	my $nest = shift || 0;

	if ('ARRAY' eq ref $obj)
	{
		my $n = 0;
		foreach my $item (@$obj)
			{ $n += $this->parseJson($item) if (('HASH' eq ref $item) || ('ARRAY' eq ref $item)); }
		return $n;
	}
	
	return 0 if ('HASH' ne ref $obj);
	return 0 if (! keys %$obj);
	
	if ($obj->{'$schema'}->{'$ref'} eq 'http://soapjr.org/schemas/RDF_JSON')
	{
		return $this->parseRdfObject($obj);
	}
	elsif (length $obj->{'$transformation'})
	{
		return $this->doGrddl($obj, $this->uri($obj->{'$transformation'}));
	}
	elsif (length $obj->{'$schema'}->{'$schemaTransformation'})
	{
		return $this->doGrddl($obj, $this->uri($obj->{'$schema'}->{'$schemaTransformation'}));
	}
	elsif (length $obj->{'$schema'}->{'$ref'})
	{
		my $r  = _get($this, $this->uri($obj->{'$schema'}->{'$ref'}),
			'application/schema+json, application/x-schema+json, application/json');
		
		if (defined $r
		&&  $r->code == 200
		&&  $r->header('content-type') =~ m#^\s*(((application|text)/(x-)?json)|(application/(x-)?schema\+json))\b#)
		{
			my $schema = from_json($r->content);
			if (length $schema->{'$schemaTransformation'})
			{
				return $this->doGrddl($obj, $this->uri($schema->{'$schemaTransformation'}));
			}
		}
	}
	elsif (! $nest)
	{
		# Only perform this on root object - not nested objects.
		my $allKeysHaveColons = 1;
		foreach my $key (%$obj)
			{ $allKeysHaveColons = 0 unless $key =~ /:/; }
			
		return $this->parseRdfObject($obj);
	}
	
	my $n = 0;
	foreach my $item (values %$obj)
	{
		$n += $this->parseJson($item, $nest+1)
			if (('HASH' eq ref $item) || ('ARRAY' eq ref $item));
	}
	return $n;
}

1;
